#include "connection.h"
#include <QDebug>
#include <QFileDialog>
#include <QFileInfo>
#include <QMessageBox>

Connection::Connection(QTcpSocket *tcpSocket, QWidget *parent)
        : QSplitter(parent)
{
    this->socket = tcpSocket;
        this->socket->setParent(this);
        QObject::connect(this->socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
                         this, SLOT(stateChange(QAbstractSocket::SocketState)));
        QObject::connect(this->socket, SIGNAL(readyRead()), this, SLOT(receiveData()));
        QObject::connect(this->socket, SIGNAL(disconnected()), this, SLOT(endConnection()));
        QObject::connect(this->socket, SIGNAL(bytesWritten(qint64)), this, SLOT(afterSent(qint64)));

    this->streamer.setDevice(this->socket);
    this->streamer.setVersion(QDataStream::Qt_4_5);

    this->sendingIdUpto = 0;
    this->blockType = 0;

    this->chatBox = new ChatWidget(this);
        QObject::connect(this->chatBox, SIGNAL(finishedMsg()), this, SLOT(sendMsg()));
        QObject::connect(this->chatBox, SIGNAL(movedCursor(qint16)), this, SLOT(sendMove(qint16)));
        QObject::connect(this->chatBox, SIGNAL(typedBackspace()), this, SLOT(sendBackspace()));
        QObject::connect(this->chatBox, SIGNAL(typedChar(QChar)), this, SLOT(sendChar(QChar)));
        QObject::connect(this->chatBox, SIGNAL(typedDelete()), this, SLOT(sendDelete()));
        QObject::connect(this->chatBox, SIGNAL(typedString(QString)), this, SLOT(sendString(QString)));
        this->addWidget(this->chatBox);


    QWidget *transferWidget = new QWidget(this);
        this->transferLay = new QVBoxLayout(transferWidget);
        transferWidget->setLayout(this->transferLay);
        this->sendFileButton = new QPushButton(tr("Send a file"), transferWidget);
            QObject::connect(this->sendFileButton, SIGNAL(clicked()), this, SLOT(locateFile()));
            this->transferLay->addWidget(this->sendFileButton);
        this->addWidget(transferWidget);
}

Connection::~Connection()
{
    QMutableMapIterator<int, TransferBox*> i(this->sendingTransfers);
    while(i.hasNext())
    {
        i.next();
        this->removeSendingTransferBox(i.key());
    }

    QMutableMapIterator<int, TransferBox*> j(this->sendingTransfers);
    while(j.hasNext())
    {
        j.next();
        this->removeReceivingTransferBox(i.key());
    }


}


void Connection::afterSent(qint64 bytes)
{
    if(this->socket->bytesToWrite() > 0)
    {
        qDebug() << "Returning, as there is still: " << this->socket->bytesToWrite();
        return; //there is still more. Let's not overload.
    }


    //if there's files to send, lets send chunks of them

    QMutableMapIterator<int, TransferBox*> i(this->sendingTransfers);

    while(i.hasNext())
    {
        i.next();
        QByteArray data = i.value()->send();
        if(!data.isEmpty()) //if it is empty, it'll soon take care of itself
        {
            this->streamer << (quint8) Connection::FileDataFlag;
            //the id of it
            this->streamer << (quint32) i.key();
            //and the actual data
            this->streamer << data;
        }
    }
    return;
}


void Connection::receiveData()
{
restartPoint:


    if(this->blockType == 0)
    {
        //let's figure out what block  we're dealing with

        if(this->socket->bytesAvailable() < (qint64) sizeof(quint8))
        {
            //we can't even read the block type. lol.
            return;
        }
        this->streamer >> this->blockType;
    }

    switch(this->blockType)
    {
        case ChatStringFlag:
        {
            if(this->socket->bytesAvailable() < (qint64) sizeof(quint32))
                return;

            quint32 stringSize;
            QByteArray peekBytes = this->socket->peek(sizeof(quint32));
            QDataStream ds(&peekBytes, QIODevice::ReadOnly);
            ds >> stringSize;

            if(this->socket->bytesAvailable() < (qint64) sizeof(quint32) + stringSize)
                return;

            //looks like we're good to reed..
            QString msg;
            this->streamer >> msg;
            qFatal("Not implimented yet.. :(");
            break;
        }

        case ChatCharFlag:
        {
            if(this->socket->bytesAvailable() < (qint64) sizeof(QChar))
                return;
            QChar msg;
            this->streamer >> msg;
            this->chatBox->remoteWrite(msg);
            break;
        }

        case ChatBackspaceFlag:
            this->chatBox->remoteBackspace();
            break;

        case ChatEnterFlag:
            this->chatBox->remoteFinish();
            break;

        case ChatMoveFlag:
            qFatal("Case not handled yet..");
            break;

        case FileInitiateFlag:
        {
            quint32 fileId;
            quint64 fileSize;
            QString fileName;

            if(this->socket->bytesAvailable() < (qint64) (sizeof(quint32) + sizeof(quint64) + sizeof(quint32)))
                return;

            QByteArray peekBytes = this->socket->peek(sizeof(quint32) + sizeof(quint64) + sizeof(quint32));
            QDataStream ds(&peekBytes, QIODevice::ReadOnly);
                ds >> fileId >> fileSize; //just waste the bytes to get to the right spot..
                quint32 stringSize;
                ds >> stringSize;

            if(this->socket->bytesAvailable() <
               (qint64) (sizeof(quint32) + sizeof(quint64) + sizeof(quint32) + stringSize))
            {
                return;
            }

            streamer >> fileId >> fileSize >> fileName;


            //TODO: improve fileName shit drastically to make it safe
            //something like jesus.jpg to jesus_1.jpg would be ideal
            while(QFile::exists(fileName))
            {
                fileName = "x" + fileName;
            }
            qDebug() << "file init recieved. id: " << fileId << " fileName: " << fileName << " filesize: " << fileSize;

            TransferBox *box = new TransferBox(fileId, fileName, fileSize, this);
                QObject::connect(box, SIGNAL(remove(int)), this, SLOT(removeReceivingTransferBox(int)));
                this->receivingTransfers.insert(fileId, box);
                this->transferLay->addWidget(box);
            break;
        }
        case FileDataFlag:
        {
            quint32 fileId;
            QByteArray data;

             if(this->socket->bytesAvailable() < (qint64) (sizeof(quint32) + sizeof(quint32)))
                return;

            QByteArray peekBytes = this->socket->peek(sizeof(quint32) + sizeof(quint32));
            QDataStream ds(&peekBytes, QIODevice::ReadOnly);
                ds >> fileId; //just waste the bytes to get to the right spot..
                quint32 dataSize;
                ds >> dataSize;

            if(this->socket->bytesAvailable() < (qint64) sizeof(quint32) + dataSize)
                return;

            this->streamer >> fileId >> data;

            Q_ASSERT(this->receivingTransfers.contains(fileId));

            this->receivingTransfers[fileId]->receive(data);

            break;
        }

        case FileFinishFlag:
        {
            quint32 fileId;

            if(this->socket->bytesAvailable() < (qint64) sizeof(quint32))
                return;

            this->streamer >> fileId;
            qDebug() << "File Id: " << fileId << " has finished sending.";
            this->receivingTransfers.value(fileId)->finishedTransfer();
            break;
        }

        case FileCancelFlag:
         {
            quint32 fileId;

            if(this->socket->bytesAvailable() < (qint64) sizeof(quint32))
                return;

            this->streamer >> fileId;
            qDebug() << "File " << fileId << " has been canceled.";
            this->receivingTransfers.value(fileId)->cancelTransfer();
            break;

        }
        default:
            qFatal("Unknown block type..");

    }
    this->blockType = 0;
    goto restartPoint; //incase more is queued
}

void Connection::transferFinished(int id)
{
    this->streamer << (quint8) Connection::FileFinishFlag;
    this->streamer << (quint32) id;

    this->removeSendingTransferBox(id);
    //TODO: notification of transfer completed
}

void Connection::transferCanceled(int id)
{
    this->streamer << (quint8) Connection::FileCancelFlag;
    this->streamer << (quint32) id;

    this->removeSendingTransferBox(id);
}

void Connection::removeSendingTransferBox(int id)
{
    delete this->sendingTransfers.value(id);
    this->sendingTransfers.remove(id);
}

void Connection::removeReceivingTransferBox(int id)
{
    delete this->receivingTransfers.value(id);
    this->receivingTransfers.remove(id);
}


void Connection::stateChange(QAbstractSocket::SocketState socketState)
{
    switch (socketState)
    {
    case QAbstractSocket::UnconnectedState:
        qDebug("Socket in unconnected state");
        break;
    case QAbstractSocket::HostLookupState:
        qDebug("Socket is looking up host :o");
        break;
    case QAbstractSocket::ConnectingState:
        qDebug("Socket is connecting..");
        break;
    case QAbstractSocket::ConnectedState:
        qDebug("Changed to connected");
        break;
    case QAbstractSocket::BoundState:
        qFatal("Socket is bound. wth?");
        break;
    case QAbstractSocket::ClosingState:
        qDebug("Socket entering closing state");
        break;
    case QAbstractSocket::ListeningState:
        qFatal("Why are we in a listen state?");
        break;
    default:
        qFatal("Hit impossible network state??");
    }
}

void Connection::endConnection()
{
    qDebug() << "Ending connection :o";
    //TODO: provide visual stimulus
    this->chatBox->setEnabled(false);
}

void Connection::sendChar(QChar msg)   //User has just hit enter
{
    this->streamer << (quint8) Connection::ChatCharFlag;
    this->streamer << msg;
}
void Connection::sendString(QString msg)
{
    this->streamer << (quint8) Connection::ChatStringFlag;
    this->streamer << msg;

}
void Connection::sendBackspace()
{
    this->streamer << (quint8) Connection::ChatBackspaceFlag;
}
void Connection::sendDelete()
{
    this->streamer << (quint8) Connection::ChatBackspaceFlag;
}
void Connection::sendMove(qint16 dir)
{
    this->streamer << (quint8) Connection::ChatMoveFlag;
    this->streamer << dir;
}
void Connection::sendMsg()
{
    this->streamer << (quint8) Connection::ChatEnterFlag;
}
void Connection::locateFile()
{
    QString filepath = QFileDialog::getOpenFileName(this, tr("Locate the file you wish to send"));
    if(filepath.isNull() || filepath.isEmpty())
        return; //faggots wasting our god damn time

    QFileInfo zeFile(filepath);

    if(!zeFile.isFile())
    {
        QMessageBox::information(this, tr("Error!"), tr("%1 is not a file?").arg(filepath));
        return;
    }

    if(!zeFile.isReadable())
    {
        QMessageBox::information(this, tr("Error!"), tr("Unable to read file: %1").arg(filepath));
        return;
    }

    quint64 filesize = zeFile.size();
    if(filesize == 0)
    {
        QMessageBox::information(this, tr("Error!"), tr("Filesize is zero. Trying to read a device or something? %1").arg(filepath));
        return;
    }


    TransferBox *box = new TransferBox(++(this->sendingIdUpto), filepath, this);
        QObject::connect(box, SIGNAL(finishedSending(int)), this, SLOT(transferFinished(int)));
        QObject::connect(box, SIGNAL(remove(int)), this, SLOT(removeReceivingTransferBox(int)));
        QObject::connect(box, SIGNAL(cancel(int)), this, SLOT(transferCanceled(int)));
        this->sendingTransfers.insert(this->sendingIdUpto, box);
        this->transferLay->addWidget(box);


     //what's the size of what's to come?
     this->streamer << (quint8) Connection::FileInitiateFlag;
     this->streamer << (quint32) this->sendingIdUpto;
     this->streamer << (quint64) filesize;
     this->streamer << zeFile.fileName();

}
